﻿/**
 * http://pooyakhamooshi.blogspot.com/2009/11/create-thread-safe-queued.html
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.ComponentModel;

namespace RedFoxSpamKiller
{
  /// <summary>
  /// This is thread-safe
  /// </summary>
  public class QueuedBackgroundWorker
  {
    #region Constructors

    public QueuedBackgroundWorker() { }

    #endregion

    #region Properties

    Queue<object> Queue = new Queue<object>();

    object lockingObject1 = new object();

    public delegate void WorkerCompletedDelegate<K>(K result, Exception error);
    public delegate void WorkerProgressChangedDelegate(int progressPercentage);

    #endregion

    #region Methods

    /// <summary>
    /// doWork is a method with one argument
    /// </summary>
    /// <typeparam name="T">is the type of the input parameter</typeparam>
    /// <typeparam name="K">is the type of the output result</typeparam>
    /// <param name="inputArgument"></param>
    /// <param name="doWork"></param>
    /// <param name="workerCompleted"></param>
    public void RunAsync<S, T, K>(Func<S, T, K> doWork, T inputArgument, WorkerProgressChangedDelegate progressChanged, WorkerCompletedDelegate<K> workerCompleted)
    {
      BackgroundWorker bw = GetBackgroundWorker<S, T, K>(doWork, progressChanged, workerCompleted);

      Queue.Enqueue(new QueueItem(bw, inputArgument));

      lock (lockingObject1)
      {
        if (Queue.Count == 1)
        {
          ((QueueItem)this.Queue.Peek()).RunWorkerAsync();
        }
      }
    }

    /// <summary>
    /// Use this method if you don't need to handle when the worker is completed
    /// </summary>
    /// <param name="doWork"></param>
    /// <param name="inputArgument"></param>
    public void RunAsync<S, T, K>(Func<S, T, K> doWork, T inputArgument)
    {
      RunAsync(doWork, inputArgument, null, null);
    }

    private BackgroundWorker GetBackgroundWorker<S, T, K>(Func<S, T, K> doWork, WorkerProgressChangedDelegate progressChanged, WorkerCompletedDelegate<K> workerCompleted)
    {
      BackgroundWorker bw = new BackgroundWorker();
      bw.WorkerReportsProgress = true;
      bw.WorkerSupportsCancellation = false;

      bw.DoWork += (sender, args) =>
      {
        if (doWork != null)
        {
          args.Result = (K)doWork((S)sender, (T)args.Argument);
        }
      };

      bw.ProgressChanged += (sender, args) =>
        {
          if (progressChanged != null)
            progressChanged(args.ProgressPercentage);
        };

      bw.RunWorkerCompleted += (sender, args) =>
      {
        if (workerCompleted != null)
        {
          workerCompleted((K)args.Result, args.Error);
        }
        Queue.Dequeue();
        lock (lockingObject1)
        {
          if (Queue.Count > 0)
          {
            ((QueueItem)this.Queue.Peek()).RunWorkerAsync();
          }
        }
      };
      return bw;
    }

    #endregion
  }

  public class QueueItem
  {
    #region Constructors

    public QueueItem(BackgroundWorker backgroundWorker, object argument)
    {
      this.BackgroundWorker = backgroundWorker;
      this.Argument = argument;
    }

    #endregion

    #region Properties

    public object Argument { get; private set; }

    public BackgroundWorker BackgroundWorker { get; private set; }

    #endregion

    #region Methods

    public void RunWorkerAsync()
    {
      this.BackgroundWorker.RunWorkerAsync(this.Argument);
    }

    #endregion
  }
}